using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using FireStarter.Base;
using FireStarterModeller.BO;
using Habanero.Base;
using Habanero.Base.Exceptions;
using Habanero.BO;

namespace FireStarterModeller.Logic
{
    public enum RelationshipCardinality
    {
        OneToMany,
        ManyToOne,
        OneToOne
    }
    // ReSharper disable VirtualMemberNeverOverriden.Global
    public class DMRelationshipCreator
    {
        private readonly BusinessObjectCollection<DMProperty> _createdDMProperties;

        public DMRelationshipCreator(IDMClass ownerClass)
        {
            if (ownerClass == null) throw new ArgumentNullException("ownerClass");
            Relationship = new DMRelationship {ClassDM = ownerClass};
            _createdDMProperties = new BusinessObjectCollection<DMProperty>();
        }

        public DMRelationship Relationship { get; private set; }

        public static string FkPropertySuffix
        {
            get { return "ID"; }
        }

        public IDMClass RelatedDMClass
        {
            set
            {
                if (this.Relationship.ReverseRelationship == null)
                {
                    throw new HabaneroApplicationException("The Reverse Relationship for '" + this.Relationship +
                                                           "' should never be null");
                }
                this.Relationship.ReverseRelationship.ClassDM = value;
            }
            get
            {
                if (this.Relationship.ReverseRelationship == null)
                {
                    throw new HabaneroApplicationException("The Reverse Relationship for '" + this.Relationship +
                                                           "' should never be null");
                }
                return this.Relationship.ReverseRelationship.ClassDM;
            }
        }

        /// <summary>
        /// If there is a Property on the Related Class that matches the Foreign key rules
        /// then it will return this.
        /// </summary>
        /// <returns></returns>
        public DMProperty FindRelatedForeignKeyProp()
        {
            return FindForeignKeyProp( this.OwnerClass, this.RelatedClass);
        }

        /// <summary>
        /// If there is a key on the owner Class that matches the foreign key rules
        /// then it will return this
        /// </summary>
        /// <returns></returns>
        public DMProperty FindOwnerForeignKeyProp()
        {
//            Contract.Requires(this.OwnerClass != null);
//            Contract.Requires(this.RelatedClass != null);
            return FindForeignKeyProp(this.RelatedClass, this.OwnerClass);
        }

        public virtual bool CanFindForeignKey
        {
            get
            {
                if (IsRelationshipSetup) return true;
                return (this.FindRelatedForeignKeyProp() != null
                        || this.FindOwnerForeignKeyProp() != null);
            }
        }

        private static DMProperty FindForeignKeyProp(IDMClass ownerClass, IDMClass relatedClass)
        {
            if (ownerClass == null || relatedClass == null) return null;
            var foundForeignKeyProp = relatedClass.FindPropByName(ownerClass.ClassNameBO + FkPropertySuffix);

            if (foundForeignKeyProp == null 
                    && ownerClass.ObjectIdentity != null 
                    && ownerClass.ObjectIdentity.Properties.Count == 1)
            {
                DMProperty primaryKeyProp = ownerClass.ObjectIdentity.Properties[0];
                foundForeignKeyProp = relatedClass.FindPropByName(primaryKeyProp.PropertyName);
            }
            if (foundForeignKeyProp != null)
            {
                if (foundForeignKeyProp.IsPartOfRelationshipToClass(ownerClass))
                {
                    return null;
                }
            }

            return foundForeignKeyProp;
        }

        public virtual void SetupRelationship()
        {
            DMProperty foundRelatedForeignKeyProp = this.FindRelatedForeignKeyProp();
            //TODO brett 22 Oct 2009: Must check that the found Related FK Prop is the correct type.
            if (foundRelatedForeignKeyProp != null)
            {

                SetupRelationshipProps(this.RelatedClass, this.Relationship, foundRelatedForeignKeyProp);
            }
            else
            {
                //Test that a change of cardinality is tested. Currently it seems that once the OwnerForeignKey is found
                // the relationship is setup without checking cardinality changes. 
                DMProperty foundOwnerForeignKeyProp = this.FindOwnerForeignKeyProp();
                if (foundOwnerForeignKeyProp != null)
                {
                    SetupRelationshipProps(this.OwnerClass, (DMRelationship) this.Relationship.ReverseRelationship,
                                           foundOwnerForeignKeyProp);
                }
                else if (MustCreateForeignKeyProp)
                {
                    if (MustCreateFKInRelatedClass())
                    {
                        DMProperty dmProperty = CreateFKProperty(this.OwnerClass, this.RelatedClass);
                        SetupRelationshipProps(this.RelatedClass, this.Relationship, dmProperty);
                    }
                    else
                    {
                        DMProperty dmProperty = CreateFKProperty(this.RelatedClass, this.OwnerClass);
                        SetupRelationshipProps(this.OwnerClass, (DMRelationship) this.Relationship.ReverseRelationship, dmProperty);
                    }
                }
                else
                {
                    if (this.RelationshipCardinality == Logic.RelationshipCardinality.OneToMany)
                    {
                        SetupRelationship(this.RelatedClass, this.Relationship);
                    }
                    else
                    {
                        SetupRelationship(this.OwnerClass, (DMRelationship) this.Relationship.ReverseRelationship);
                    }
                }
            }
            //ToDo: Brett 31/07/2010 Should move this to a factory so that can swap naming strategies.
            var relNameGen =new RelationshipNameGenerator();
            this.Relationship.RelationshipName = relNameGen.CreateRelationshipName(this.Relationship);
            this.Relationship.ReverseRelationship.RelationshipName = relNameGen.CreateRelationshipName(this.Relationship.ReverseRelationship);
            IsRelationshipSetup = true;
        }

        private bool MustCreateFKInRelatedClass()
        {
            return this.RelationshipCardinality == Logic.RelationshipCardinality.OneToMany ||
                   this.RelationshipCardinality == Logic.RelationshipCardinality.OneToOne;
        }

        protected DMProperty CreateFKProperty(IDMClass ownerClass, IDMClass relatedClass)
        {
            DMProperty createdRelatedFKProp = relatedClass.Properties.CreateBusinessObject();
            createdRelatedFKProp.PropertyName = ownerClass + FkPropertySuffix;
            DMProperty objectIDProp = GetObjectIDProp();
            if (objectIDProp != null)
            {
                createdRelatedFKProp.PropertyType = objectIDProp.PropertyType;
            }
            _createdDMProperties.Add(createdRelatedFKProp);

            if (this.RelationshipCardinality == Logic.RelationshipCardinality.OneToOne)
            {
                relatedClass.CreateUnsavedUniqueConstraint(new List<DMProperty> { createdRelatedFKProp });
            }

            return createdRelatedFKProp;
        }

        private DMProperty GetObjectIDProp()
        {
            DMObjectIdentity dmObjectIdentity = this.OwnerClass.GetObjectID();
            return dmObjectIdentity == null ? null : dmObjectIdentity.Properties[0];
        }

        private void SetupRelationshipProps(IDMClass classWithFK, DMRelationship relationshipToFK,
                                                   DMProperty foreignKeyProp)
        {
            if (foreignKeyProp.IsPartOfSingleUniqueConstraint)
            {
                this.RelationshipCardinality = Logic.RelationshipCardinality.OneToOne;
            }
            
            SetupRelationship(classWithFK, relationshipToFK);

            DMRelationshipProperty relationshipProperty = relationshipToFK.RelationshipProperties.CreateBusinessObject();
            relationshipProperty.RelatedProperty = foreignKeyProp;
            if (relationshipToFK.OwnerClass.GetObjectID() != null)
            {
                relationshipProperty.Property = relationshipToFK.OwnerClass.GetObjectID().Properties[0];
            }
        }

        private void SetupRelationship(IDMClass classWithFK, DMRelationship relationshipToFK)
        {
            if (this.RelationshipCardinality == Logic.RelationshipCardinality.OneToOne)
            {
                relationshipToFK.Cardinality = Cardinality.Single;
//                relationshipToFK.RelationshipName = StringUtilities.Singularize(classWithFK.ClassNameBO);
            } else
            {
                relationshipToFK.Cardinality = Cardinality.Multiple;
//                relationshipToFK.RelationshipName = StringUtilities.Pluralize(classWithFK.ClassNameBO);
            }
            
            DMRelationship reverseRelationship = (DMRelationship) relationshipToFK.ReverseRelationship;
            reverseRelationship.Cardinality = Cardinality.Single;
            reverseRelationship.DeleteAction = DeleteParentAction.DoNothing;


//            reverseRelationship.RelationshipName = StringUtilities.Singularize(relationshipToFK.OwnerClass.ClassNameBO);
        }

        private IDMClass RelatedClass
        {
            get { return this.Relationship.RelatedClass; }
        }

        private IDMClass OwnerClass
        {
            get { return this.Relationship.OwnerClass; }
        }

        public bool MustCreateForeignKeyProp { get; set; }

        public RelationshipCardinality? RelationshipCardinality { get; set; }

        public bool AreObjectIDsValid
        {
            get { return this.Relationship == null ? false : this.Relationship.AreObjectIDsValid; }
        }

        public bool CanCreateForeignKey
        {
            get
            {
                return this.AreObjectIDsValid && this.OwnerClass.PrimaryKeyIsID
                       && this.RelatedClass.PrimaryKeyIsID && !this.CanFindForeignKey;
            }
        }

        public bool HasExistingRelationshipBetweenClasses
        {
            get
            {
                var relationships = this.Relationship.OwnerClass.RelationshipsDM;
                return relationships
                    .Where(relationship => this.Relationship != relationship)
                    .Any(relationship => this.Relationship.RelatedClass == relationship.RelatedClass);
            }
        }

        public virtual bool IsRelationshipSetup { get; private set; }

        public virtual void CancelCreatedProperties()
        {
            foreach (var relationshipProperty in this.Relationship.RelationshipProperties.ToArray())
            {
                relationshipProperty.MarkForDelete();
            }
            foreach (DMProperty createdFKProperty in _createdDMProperties.ToArray())
            {
                createdFKProperty.RelationshipProperties.CancelEdits();
                createdFKProperty.MarkForDelete();
            }
            this.Relationship.RelationshipName = null;
            this.Relationship.ReverseRelationship.RelationshipName = null;
            IsRelationshipSetup = false;
        }

        public virtual void CancelAllEdits()
        {
            this.CancelCreatedProperties();
            this.Relationship.OwnerClass = null;
            this.Relationship.RelatedClass = null;
            this.Relationship.ReverseRelationship.OwnerClass = null;
            this.Relationship.ReverseRelationship.RelatedClass = null;
            this.Relationship.MarkForDelete();
            this.Relationship.ReverseRelationship.MarkForDelete();
        }
    }
    // ReSharper restore VirtualMemberNeverOverriden.Global
}